<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>Prototype Unit test file</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <script src="../../dist/prototype.js" type="text/javascript"></script>
  <script src="../lib/unittest.js" type="text/javascript"></script>
  <link rel="stylesheet" href="../test.css" type="text/css" />
  <style type="text/css" media="screen">
  /* <![CDATA[ */
    #testcss1 { font-size:11px; color: #f00; }
    #testcss2 { font-size:12px; color: #0f0; display: none; }
  /* ]]> */
  </style>
</head>
<body>
<h1>Prototype Unit test file</h1>
<p>
  Test of utility functions in base.js
</p>

<!-- Log output -->
<div id="testlog"> </div>
<div id="test"></div> 
<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
  var Person = function(name){
      this.name = name;
  };
  
  Person.prototype.toJSON = function() {
    return '-' + this.name;
  };

  var arg1 = 1;
  var arg2 = 2;
  var arg3 = 3;
  function TestObj() { };
  TestObj.prototype.assertingEventHandler = 
    function(event, assertEvent, assert1, assert2, assert3, a1, a2, a3) {
      assertEvent(event);
      assert1(a1);
      assert2(a2);
      assert3(a3);
    };
    
  var globalBindTest = null;
  
  
  // base class
  var Animal = Class.create({
    initialize: function(name) {
      this.name = name;
    },
    name: "",
    eat: function() {
      return this.say("Yum!");
    },
    say: function(message) {
      return this.name + ": " + message;
    }
  });

  // subclass that augments a method
  var Cat = Class.create(Animal, {
    eat: function($super, food) {
      if (food instanceof Mouse) return $super();
      else return this.say("Yuk! I only eat mice.");
    }
  });

  // empty subclass
  var Mouse = Class.create(Animal, {});
  
  //mixins 
  var Sellable = {
    getValue: function(pricePerKilo) {
      return this.weight * pricePerKilo;
    },
    
    inspect: function() {
      return '#<Sellable: #{weight}kg>'.interpolate(this);
    }
  };

  var Reproduceable = {
    reproduce: function(partner) {
      if (partner.constructor != this.constructor || partner.sex == this.sex)
        return null;
      var weight = this.weight / 10, sex = Math.random(1).round() ? 'male' : 'female';
      return new this.constructor('baby', weight, sex);
    }
  };
  
  // base class with mixin
  var Plant = Class.create(Sellable, {
    initialize: function(name, weight) {
      this.name = name;
      this.weight = weight;
    },

    inspect: function() {
      return '#<Plant: #{name}>'.interpolate(this);
    }
  });
  
  // subclass with mixin
  var Dog = Class.create(Animal, Reproduceable, {
    initialize: function($super, name, weight, sex) {
      this.weight = weight;
      this.sex = sex;
      $super(name);
    }
  });
  
  // subclass with mixins
  var Ox = Class.create(Animal, Sellable, Reproduceable, {
    initialize: function($super, name, weight, sex) {
      this.weight = weight;
      this.sex = sex;
      $super(name);
    },
    
    eat: function(food) {
      if (food instanceof Plant)
        this.weight += food.weight;
    },
    
    inspect: function() {
      return '#<Ox: #{name}>'.interpolate(this);
    }
  });
  
  new Test.Unit.Runner({
    
    testFunctionArgumentNames: function() { with(this) {
      assertEnumEqual([], (function() {}).argumentNames());
      assertEnumEqual(["one"], (function(one) {}).argumentNames());
      assertEnumEqual(["one", "two", "three"], (function(one, two, three) {}).argumentNames());
      assertEqual("$super", (function($super) {}).argumentNames().first());
      
      function named1() {};
      assertEnumEqual([], named1.argumentNames());
      function named2(one) {};
      assertEnumEqual(["one"], named2.argumentNames());
      function named3(one, two, three) {};
      assertEnumEqual(["one", "two", "three"], named3.argumentNames());
    }},
    
    testFunctionBind: function() { with(this) {
      function methodWithoutArguments() { return this.hi };
      function methodWithArguments()    { return this.hi + ',' + $A(arguments).join(',') };
      var func = Prototype.emptyFunction;

      assertIdentical(func, func.bind());
      assertIdentical(func, func.bind(undefined));
      assertNotIdentical(func, func.bind(null));

      assertEqual('without', methodWithoutArguments.bind({ hi: 'without' })());
      assertEqual('with,arg1,arg2', methodWithArguments.bind({ hi: 'with' })('arg1','arg2'));
      assertEqual('withBindArgs,arg1,arg2',
        methodWithArguments.bind({ hi: 'withBindArgs' }, 'arg1', 'arg2')());
      assertEqual('withBindArgsAndArgs,arg1,arg2,arg3,arg4',
        methodWithArguments.bind({ hi: 'withBindArgsAndArgs' }, 'arg1', 'arg2')('arg3', 'arg4'));
    }},
    
    testFunctionCurry: function() { with(this) {
      var split = function(delimiter, string) { return string.split(delimiter); };
      var splitOnColons = split.curry(":");
      assertNotIdentical(split, splitOnColons);
      assertEnumEqual(split(":", "0:1:2:3:4:5"), splitOnColons("0:1:2:3:4:5"));
      assertIdentical(split, split.curry());
    }},
    
    testFunctionDelay: function() { with(this) {
      window.delayed = undefined;
      var delayedFunction = function() { window.delayed = true; };
      var delayedFunctionWithArgs = function() { window.delayedWithArgs = $A(arguments).join(' '); };
      delayedFunction.delay(0.8);
      delayedFunctionWithArgs.delay(0.8, 'hello', 'world');
      assertUndefined(window.delayed);
      wait(1000, function() {
        assert(window.delayed);
        assertEqual('hello world', window.delayedWithArgs);
      });
    }},
    
    testFunctionWrap: function() { with(this) {
      function sayHello(){
        return 'hello world';
      };
      
      assertEqual('HELLO WORLD', sayHello.wrap(function(proceed) {
        return proceed().toUpperCase();
      })());
      
      var temp = String.prototype.capitalize;
      String.prototype.capitalize = String.prototype.capitalize.wrap(function(proceed, eachWord) {
        if(eachWord && this.include(' ')) return this.split(' ').map(function(str){
          return str.capitalize();
        }).join(' ');
        return proceed();
      });
      assertEqual('Hello world', 'hello world'.capitalize());
      assertEqual('Hello World', 'hello world'.capitalize(true));
      assertEqual('Hello', 'hello'.capitalize());
      String.prototype.capitalize = temp;
    }},
    
    testFunctionDefer: function() { with(this) {
      window.deferred = undefined;
      var deferredFunction = function() { window.deferred = true; };
      deferredFunction.defer();
      assertUndefined(window.deferred);
      wait(50, function() {
        assert(window.deferred);
      });
    }},
    
    testFunctionMethodize: function() { with(this) {
      var Foo = { bar: function(baz) { return baz } };
      var baz = { quux: Foo.bar.methodize() };
      
      assertEqual(Foo.bar.methodize(), baz.quux);
      assertEqual(baz, Foo.bar(baz));
      assertEqual(baz, baz.quux());
    }},

    testObjectExtend: function() { with(this) {
      var object = {foo: 'foo', bar: [1, 2, 3]};
      assertIdentical(object, Object.extend(object));
      assertHashEqual({foo: 'foo', bar: [1, 2, 3]}, object);
      assertIdentical(object, Object.extend(object, {bla: 123}));
      assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: 123}, object);
      assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: null},
        Object.extend(object, {bla: null}));
    }},
    
    testObjectToQueryString: function() { with(this) {
      assertEqual('a=A&b=B&c=C&d=D%23', Object.toQueryString({a: 'A', b: 'B', c: 'C', d: 'D#'}));
    }},
    
    testObjectClone: function() { with(this) {
      var object = {foo: 'foo', bar: [1, 2, 3]};
      assertNotIdentical(object, Object.clone(object));
      assertHashEqual(object, Object.clone(object));
      assertHashEqual({}, Object.clone());
      var clone = Object.clone(object);
      delete clone.bar;
      assertHashEqual({foo: 'foo'}, clone, 
        "Optimizing Object.clone perf using prototyping doesn't allow properties to be deleted.");
    }},

    testObjectInspect: function() { with(this) {
      assertEqual('undefined', Object.inspect());
      assertEqual('undefined', Object.inspect(undefined));
      assertEqual('null', Object.inspect(null));
      assertEqual("'foo\\\\b\\\'ar'", Object.inspect('foo\\b\'ar'));
      assertEqual('[]', Object.inspect([]));
    }},
    
    testObjectToJSON: function() { with(this) {
      assertUndefined(Object.toJSON(undefined));
      assertUndefined(Object.toJSON(Prototype.K));
      assertEqual('\"\"', Object.toJSON(''));
      assertEqual('[]', Object.toJSON([]));
      assertEqual('[\"a\"]', Object.toJSON(['a']));
      assertEqual('[\"a\", 1]', Object.toJSON(['a', 1]));
      assertEqual('[\"a\", {\"b\": null}]', Object.toJSON(['a', {'b': null}]));
      assertEqual('{\"a\": \"hello!\"}', Object.toJSON({a: 'hello!'}));
      assertEqual('{}', Object.toJSON({}));
      assertEqual('{}', Object.toJSON({a: undefined, b: undefined, c: Prototype.K}));
      assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}',
        Object.toJSON({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}));
      assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}',
        Object.toJSON($H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}})));
      assertEqual('true', Object.toJSON(true));
      assertEqual('false', Object.toJSON(false));
      assertEqual('null', Object.toJSON(null));
      var sam = new Person('sam');
      assertEqual('-sam', Object.toJSON(sam));
      assertEqual('-sam', sam.toJSON());
      var element = $('test');
      assertUndefined(Object.toJSON(element));
      element.toJSON = function(){return 'I\'m a div with id test'};
      assertEqual('I\'m a div with id test', Object.toJSON(element));
    }},
    
    testObjectToHTML: function() { with(this) {
      assertIdentical('', Object.toHTML());
      assertIdentical('', Object.toHTML(''));
      assertIdentical('', Object.toHTML(null));
      assertIdentical('0', Object.toHTML(0));
      assertIdentical('123', Object.toHTML(123));
      assertEqual('hello world', Object.toHTML('hello world'));
      assertEqual('hello world', Object.toHTML({toHTML: function() { return 'hello world' }}));
    }},
    
    testObjectIsArray: function() { with(this) {
      assert(Object.isArray([]));
      assert(Object.isArray([0]));
      assert(Object.isArray([0, 1]));
      assert(!Object.isArray({}));
    }},
    
    testObjectIsHash: function() { with(this) {
      assert(Object.isHash($H()));
      assert(Object.isHash(new Hash()));
      assert(!Object.isHash({}));
    }},
    
    testObjectIsElement: function() { with(this) {
      assert(Object.isElement(document.createElement('div')));
      assert(Object.isElement(new Element('div')));
      assert(Object.isElement($('testlog')));
      assert(!Object.isElement(document.createTextNode('bla')));
    }},
    
    testObjectIsFunction: function() { with(this) {
      assert(Object.isFunction(function() { }));
      assert(Object.isFunction(Class.create()));
      assert(!Object.isFunction("a string"));
      assert(!Object.isFunction($("testlog")));
      assert(!Object.isFunction([]));
      assert(!Object.isFunction({}));
      assert(!Object.isFunction(0));
      assert(!Object.isFunction(false));
      assert(!Object.isFunction(undefined));
    }},
    
    testObjectIsString: function() { with(this) {
      assert(!Object.isString(function() { }));
      assert(Object.isString("a string"));
      assert(!Object.isString(0));
      assert(!Object.isString([]));
      assert(!Object.isString({}));
      assert(!Object.isString(false));
      assert(!Object.isString(undefined));
    }},
    
    testObjectIsNumber: function() { with(this) {
      assert(Object.isNumber(0));
      assert(Object.isNumber(1.0));
      assert(!Object.isNumber(function() { }));
      assert(!Object.isNumber("a string"));
      assert(!Object.isNumber([]));
      assert(!Object.isNumber({}));
      assert(!Object.isNumber(false));
      assert(!Object.isNumber(undefined));
    }},
    
    testObjectIsUndefined: function() { with(this) {
      assert(Object.isUndefined(undefined));
      assert(!Object.isUndefined(null));
      assert(!Object.isUndefined(false));
      assert(!Object.isUndefined(0));
      assert(!Object.isUndefined(""));
      assert(!Object.isUndefined(function() { }));
      assert(!Object.isUndefined([]));
      assert(!Object.isUndefined({}));
    }},
    
    // sanity check
    testDoesntExtendObjectPrototype: function() {with(this) {
      // for-in is supported with objects
      var iterations = 0, obj = { a: 1, b: 2, c: 3 };
      for(property in obj) iterations++;
      assertEqual(3, iterations);
      
      // for-in is not supported with arrays
      iterations = 0;
      var arr = [1,2,3];
      for(property in arr) iterations++;
      assert(iterations > 3);
    }},
    
    testPeriodicalExecuterStop: function() {with(this) {
      var peEventCount = 0;
      function peEventFired(pe) {
        if (++peEventCount > 2) pe.stop();
      }
      
      // peEventFired will stop the PeriodicalExecuter after 3 callbacks
      new PeriodicalExecuter(peEventFired, 0.05);
      
      wait(600, function() {
        assertEqual(3, peEventCount);
      });
    }},

    testBindAsEventListener: function() {
      for( var i = 0; i < 10; ++i ){
        var div = document.createElement('div');
        div.setAttribute('id','test-'+i);
        document.body.appendChild(div);
        var tobj = new TestObj();
        var eventTest = { test: true };
        var call = tobj.assertingEventHandler.bindAsEventListener(tobj,
          this.assertEqual.bind(this, eventTest),
          this.assertEqual.bind(this, arg1),
          this.assertEqual.bind(this, arg2),
          this.assertEqual.bind(this, arg3), arg1, arg2, arg3 );
        call(eventTest);
      }
    },
    
    testDateToJSON: function() {with(this) {
      assertEqual('\"1970-01-01T00:00:00Z\"', new Date(Date.UTC(1970, 0, 1)).toJSON());
    }},
    
    testRegExpEscape: function() {with(this) {
      assertEqual('word', RegExp.escape('word'));
      assertEqual('\\/slashes\\/', RegExp.escape('/slashes/'));
      assertEqual('\\\\backslashes\\\\', RegExp.escape('\\backslashes\\'));
      assertEqual('\\\\border of word', RegExp.escape('\\border of word'));
      
      assertEqual('\\(\\?\\:non-capturing\\)', RegExp.escape('(?:non-capturing)'));
      assertEqual('non-capturing', new RegExp(RegExp.escape('(?:') + '([^)]+)').exec('(?:non-capturing)')[1]);
      
      assertEqual('\\(\\?\\=positive-lookahead\\)', RegExp.escape('(?=positive-lookahead)'));
      assertEqual('positive-lookahead', new RegExp(RegExp.escape('(?=') + '([^)]+)').exec('(?=positive-lookahead)')[1]);
      
      assertEqual('\\(\\?<\\=positive-lookbehind\\)', RegExp.escape('(?<=positive-lookbehind)'));
      assertEqual('positive-lookbehind', new RegExp(RegExp.escape('(?<=') + '([^)]+)').exec('(?<=positive-lookbehind)')[1]);
      
      assertEqual('\\(\\?\\!negative-lookahead\\)', RegExp.escape('(?!negative-lookahead)'));
      assertEqual('negative-lookahead', new RegExp(RegExp.escape('(?!') + '([^)]+)').exec('(?!negative-lookahead)')[1]);
      
      assertEqual('\\(\\?<\\!negative-lookbehind\\)', RegExp.escape('(?<!negative-lookbehind)'));
      assertEqual('negative-lookbehind', new RegExp(RegExp.escape('(?<!') + '([^)]+)').exec('(?<!negative-lookbehind)')[1]);
      
      assertEqual('\\[\\\\w\\]\\+', RegExp.escape('[\\w]+'));
      assertEqual('character class', new RegExp(RegExp.escape('[') + '([^\\]]+)').exec('[character class]')[1]);      
      
      assertEqual('<div>', new RegExp(RegExp.escape('<div>')).exec('<td><div></td>')[0]);      
      
      assertEqual('false', RegExp.escape(false));
      assertEqual('undefined', RegExp.escape());
      assertEqual('null', RegExp.escape(null));
      assertEqual('42', RegExp.escape(42));
      
      assertEqual('\\\\n\\\\r\\\\t', RegExp.escape('\\n\\r\\t'));
      assertEqual('\n\r\t', RegExp.escape('\n\r\t'));
      assertEqual('\\{5,2\\}', RegExp.escape('{5,2}'));
      
      assertEqual(
        '\\/\\(\\[\\.\\*\\+\\?\\^\\=\\!\\:\\$\\{\\}\\(\\)\\|\\[\\\\\\]\\\\\\\/\\\\\\\\\\]\\)\\/g',
        RegExp.escape('/([.*+?^=!:${}()|[\\]\\/\\\\])/g')
      );
    }},
    
    testBrowserDetection: function() {with(this) {
      var results = $H(Prototype.Browser).map(function(engine){
        return engine;
      }).partition(function(engine){
        return engine[1] === true
      });
      var trues = results[0], falses = results[1];
      
      info('User agent string is: ' + navigator.userAgent);
      
      assert(trues.size() == 0 || trues.size() == 1, 
        'There should be only one or no browser detected.');
      
      // we should have definite trues or falses here
      trues.each(function(result){
        assert(result[1] === true);
      });
      falses.each(function(result){
        assert(result[1] === false);
      });
      
      if(navigator.userAgent.indexOf('AppleWebKit/') > -1) {
        info('Running on WebKit');
        assert(Prototype.Browser.WebKit);
      }
      
      if(!!window.opera) {
        info('Running on Opera');
        assert(Prototype.Browser.Opera);
      }
      
      if(!!(window.attachEvent && !window.opera)) {
        info('Running on IE');
        assert(Prototype.Browser.IE);
      }
      
      if(navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1) {
        info('Running on Gecko');
        assert(Prototype.Browser.Gecko);
      } 
    }},
    
    testClassCreate: function() { with(this) { 
      assert(Object.isFunction(Animal), 'Animal is not a constructor');
      assertEnumEqual([Cat, Mouse, Dog, Ox], Animal.subclasses);
      Animal.subclasses.each(function(subclass) {
        assertEqual(Animal, subclass.superclass);
      });

      var Bird = Class.create(Animal);
      assertEqual(Bird, Animal.subclasses.last());
      // for..in loop (for some reason) doesn't iterate over the constructor property in top-level classes
      assertEnumEqual(Object.keys(new Animal).sort(), Object.keys(new Bird).without('constructor').sort());
    }},

    testClassInstantiation: function() { with(this) { 
      var pet = new Animal("Nibbles");
      assertEqual("Nibbles", pet.name, "property not initialized");
      assertEqual('Nibbles: Hi!', pet.say('Hi!'));
      assertEqual(Animal, pet.constructor, "bad constructor reference");
      assertUndefined(pet.superclass);

      var Empty = Class.create();
      assert('object', typeof new Empty);
    }},

    testInheritance: function() { with(this) {
      var tom = new Cat('Tom');
      assertEqual(Cat, tom.constructor, "bad constructor reference");
      assertEqual(Animal, tom.constructor.superclass, 'bad superclass reference');
      assertEqual('Tom', tom.name);
      assertEqual('Tom: meow', tom.say('meow'));
      assertEqual('Tom: Yuk! I only eat mice.', tom.eat(new Animal));
    }},

    testSuperclassMethodCall: function() { with(this) {
      var tom = new Cat('Tom');
      assertEqual('Tom: Yum!', tom.eat(new Mouse));

      // augment the constructor and test
      var Dodo = Class.create(Animal, {
        initialize: function($super, name) {
          $super(name);
          this.extinct = true;
        },
        
        say: function($super, message) {
          return $super(message) + " honk honk";
        }
      });

      var gonzo = new Dodo('Gonzo');
      assertEqual('Gonzo', gonzo.name);
      assert(gonzo.extinct, 'Dodo birds should be extinct');
      assertEqual("Gonzo: hello honk honk", gonzo.say("hello"));
    }},

    testClassAddMethods: function() { with(this) {
      var tom   = new Cat('Tom');
      var jerry = new Mouse('Jerry');
      
      Animal.addMethods({
        sleep: function() {
          return this.say('ZZZ');
        }
      });
      
      Mouse.addMethods({
        sleep: function($super) {
          return $super() + " ... no, can't sleep! Gotta steal cheese!";
        },
        escape: function(cat) {
          return this.say('(from a mousehole) Take that, ' + cat.name + '!');
        }
      });
      
      assertEqual('Tom: ZZZ', tom.sleep(), "added instance method not available to subclass");
      assertEqual("Jerry: ZZZ ... no, can't sleep! Gotta steal cheese!", jerry.sleep());
      assertEqual("Jerry: (from a mousehole) Take that, Tom!", jerry.escape(tom));
      // insure that a method has not propagated *up* the prototype chain:
      assertUndefined(tom.escape);
      assertUndefined(new Animal().escape);
      
      Animal.addMethods({
        sleep: function() {
          return this.say('zZzZ');
        }
      });
      
      assertEqual("Jerry: zZzZ ... no, can't sleep! Gotta steal cheese!", jerry.sleep());
    }},
    
    testBaseClassWithMixin: function() { with(this) {
      var grass = new Plant('grass', 3);
      assertRespondsTo('getValue', grass);      
      assertEqual('#<Plant: grass>', grass.inspect());
    }},
    
    testSubclassWithMixin: function() { with(this) {
      var snoopy = new Dog('Snoopy', 12, 'male');
      assertRespondsTo('reproduce', snoopy);      
   }},
   
   testSubclassWithMixins: function() { with(this) {
      var cow = new Ox('cow', 400, 'female');
      assertEqual('#<Ox: cow>', cow.inspect());
      assertRespondsTo('reproduce', cow);
      assertRespondsTo('getValue', cow);
   }},
   
   testClassWithToStringAndValueOfMethods: function() { with(this) {
     var Foo = Class.create({
       toString: function() {
         return "toString";
       },
       
       valueOf: function() {
         return "valueOf";
       }
     });
     
     assertEqual("toString", new Foo().toString());
     assertEqual("valueOf", new Foo().valueOf());
   }}

  }, 'testlog');

// ]]>
</script>
</body>
</html>
